package org.dding.msc.service.impl;

import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;

import org.dding.msc.exception.ServiceRegistryException;
import org.dding.msc.service.Service;
import org.dding.msc.service.ServiceBuilder;
import org.dding.msc.service.ServiceController;
import org.dding.msc.service.ServiceListener;
import org.dding.msc.service.ServiceName;
import org.dding.msc.service.ServiceTarget;
import org.dding.msc.value.Value;

/**
 * {@link ServiceBuilder} implementation.
 * 
 * @param <T>
 *            the type of service being built
 * 
 */
class ServiceBuilderImpl<T> implements ServiceBuilder<T> {

	private final ServiceTargetImpl serviceTarget;
	private final Value<? extends Service<T>> serviceValue;
	private final ServiceName serviceName;
	private ServiceController.Mode initialMode = ServiceController.Mode.ACTIVE;
	private final Set<ServiceName> aliases = new HashSet<ServiceName>(0);

	private final Map<ServiceListener<? super T>, ServiceListener.Inheritance> listeners = new IdentityHashMap<ServiceListener<? super T>, ServiceListener.Inheritance>(
			0);
	private boolean installed = false;

	ServiceBuilderImpl(ServiceTargetImpl serviceTarget,
			final Value<? extends Service<T>> serviceValue,
			final ServiceName serviceName) {
		if (serviceTarget == null)
			throw new IllegalArgumentException("ServiceTarget can not be null");
		this.serviceTarget = serviceTarget;
		if (serviceValue == null)
			throw new IllegalArgumentException("ServiceValue can not be null");
		this.serviceValue = serviceValue;
		if (serviceName == null)
			throw new IllegalArgumentException("ServiceName can not be null");
		this.serviceName = serviceName;
	}

	@Override
	public ServiceBuilder<T> addAliases(ServiceName... aliases) {
		if (aliases != null)
			for (ServiceName alias : aliases) {
				if (alias != null && !alias.equals(serviceName)) {
					this.aliases.add(alias);
				}
			}
		return this;
	}

	@Override
	public ServiceBuilderImpl<T> setInitialMode(
			final ServiceController.Mode mode) {
		checkAlreadyInstalled();
		if (mode == null) {
			throw new IllegalArgumentException("Initial mode is null");
		}
		initialMode = mode;
		return this;
	}

	@Override
	public ServiceBuilderImpl<T> addListener(
			final ServiceListener<? super T> listener) {
		checkAlreadyInstalled();
		listeners.put(listener, ServiceListener.Inheritance.NONE);
		return this;
	}

	@Override
	public ServiceBuilderImpl<T> addListener(
			final ServiceListener<? super T>... serviceListeners) {
		checkAlreadyInstalled();
		for (ServiceListener<? super T> listener : serviceListeners) {
			listeners.put(listener, ServiceListener.Inheritance.NONE);
		}
		return this;
	}

	@Override
	public ServiceBuilderImpl<T> addListener(
			final Collection<? extends ServiceListener<? super T>> serviceListeners) {
		checkAlreadyInstalled();
		return addListenerNoCheck(ServiceListener.Inheritance.NONE,
				serviceListeners);
	}

	@Override
	public ServiceBuilder<T> addListener(
			final ServiceListener.Inheritance inheritance,
			final ServiceListener<? super T> listener) {
		checkAlreadyInstalled();
		listeners.put(listener, inheritance);
		return this;
	}

	@Override
	public ServiceBuilder<T> addListener(
			final ServiceListener.Inheritance inheritance,
			final ServiceListener<? super T>... serviceListeners) {
		checkAlreadyInstalled();
		for (ServiceListener<? super T> listener : serviceListeners) {
			listeners.put(listener, inheritance);
		}
		return this;
	}

	@Override
	public ServiceBuilder<T> addListener(
			final ServiceListener.Inheritance inheritance,
			final Collection<? extends ServiceListener<? super T>> serviceListeners) {
		checkAlreadyInstalled();
		return addListenerNoCheck(inheritance, serviceListeners);
	}

	ServiceBuilderImpl<T> addListenerNoCheck(
			final ServiceListener.Inheritance inheritance,
			final Collection<? extends ServiceListener<? super T>> serviceListeners) {
		for (ServiceListener<? super T> listener : serviceListeners) {
			listeners.put(listener, inheritance);
		}
		return this;
	}

	ServiceBuilderImpl<T> addListenerNoCheck(
			final Map<? extends ServiceListener<? super T>, ServiceListener.Inheritance> serviceListeners) {
		listeners.putAll(serviceListeners);
		return this;
	}

	private void checkAlreadyInstalled() {
		if (installed) {
			throw new IllegalStateException("ServiceBuilder already installed");
		}
	}

	@Override
	public ServiceController<T> install() throws ServiceRegistryException {
		if (installed) {
			throw new IllegalStateException(
					"ServiceBuilder is already installed");
		}
		// mark it before perform the installation,
		// so we avoid ServiceRegistryException being thrown multiple times
		installed = true;
		return serviceTarget.install(this);
	}

	Value<? extends Service<T>> getServiceValue() {
		return serviceValue;
	}

	ServiceName getName() {
		return serviceName;
	}

	ServiceName[] getAliases() {
		return aliases.toArray(new ServiceName[aliases.size()]);
	}

	Map<ServiceListener<? super T>, ServiceListener.Inheritance> getListeners() {
		return listeners;
	}

	ServiceController.Mode getInitialMode() {
		return initialMode;
	}

	ServiceTarget getTarget() {
		return serviceTarget;
	}
}
